package com.mrx.xfriendship.exception;

import com.mrx.commons.exception.BusinessException;
import com.mrx.commons.util.asserts.Asserts;
import com.mrx.xfriendship.event.event.ErrorEvent;
import com.mrx.xfriendship.model.dto.ErrorInfoDTO;
import com.mrx.xfriendship.model.dto.Result;
import org.apache.dubbo.rpc.RpcException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.stream.Collectors;

/**
 * Controller 全局异常拦截
 *
 * @author : 肖康宁
 * @since : 2022-12-08 17:45
 **/
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @Resource
    private ApplicationEventPublisher eventPublisher;

    @Resource
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    @ExceptionHandler({Exception.class})
    public Result<String> exceptionHandler(Throwable t) {
        logger.warn("出现未知异常", t);
        eventPublisher.publishEvent(new ErrorEvent(t));
        return Result.fail(Asserts.SERVER_ERROR, t.getLocalizedMessage());
    }

    @ExceptionHandler({BusinessException.class})
    public Object businessExceptionHandler(BusinessException businessException, HttpServletResponse response) {
        String message = businessException.getMessage();
        Object data = businessException.getData();
        if (data instanceof Asserts) {
            // 如果是 Asserts 抛出的异常, 那就直接使用 Result 包装下返回
            Asserts asserts = (Asserts) data;
            String assertsMsg = asserts.getMsg();
            String actualMessage = message.equals(assertsMsg) ? assertsMsg : assertsMsg + " " + message;
            return Result.fail(asserts.getCode(), actualMessage);
        } else if (data instanceof ErrorInfoDTO) {
            // 如果是 /error 拦截到的异常, 判断下是否是 404
            ErrorInfoDTO errorInfoDTO = (ErrorInfoDTO) data;
            // 判断 造成异常的 url 是否有对应的 Controller
            // 因为 SpringSecurity 的关系, 如果不做此判断会将 404 也变成 403
            boolean match = requestMappingHandlerMapping.getHandlerMethods()
                    .keySet()
                    .stream()
                    .anyMatch(it -> it.getDirectPaths().contains(errorInfoDTO.getRequestUrl()));
            // 如果没有对应的 Controller 与 造成异常的 url 绑定
            if (!match) {
                // 手动将返回状态码设置为 404
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                errorInfoDTO.setStatusCode(String.valueOf(HttpServletResponse.SC_NOT_FOUND));
            }
            return Result.fail(errorInfoDTO);
        }
        return Result.fail(businessException);
    }

    @ExceptionHandler({RpcException.class})
    public Result<?> rpcExceptionHandler(RpcException rpcException) {
        eventPublisher.publishEvent(new ErrorEvent(rpcException));
        return Result.fail(rpcException.getLocalizedMessage());
    }

    @ExceptionHandler({MissingServletRequestParameterException.class, MethodArgumentTypeMismatchException.class})
    public Result<String> missingServletRequestParameterExceptionHandler() {
        return Result.fail(Asserts.PARAMETER_ERROR);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<String> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException exception) {
        return Result.fail(
                exception.getBindingResult().getAllErrors()
                        .stream()
                        .map(DefaultMessageSourceResolvable::getDefaultMessage)
                        .collect(Collectors.joining(", "))
        );
    }

    @ExceptionHandler(RequestRejectedException.class)
    public Result<String> requestRejectedExceptionHandler() {
        return Result.fail("请求参数包含违法内容, 已被拦截!");
    }

}
